package com.dev.weixin.tools.service.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.dev.weixin.tools.models.BaseMsg;
import com.dev.weixin.tools.models.EncryptMsg;
import com.dev.weixin.tools.models.WeixinConfig;
import com.dev.weixin.tools.models.base.Handler;
import com.dev.weixin.tools.models.exceptions.ErrorMsgException;
import com.dev.weixin.tools.models.receive.EventMsg;
import com.dev.weixin.tools.models.receive.ValidData;
import com.dev.weixin.tools.models.receive.common.ImageMsg;
import com.dev.weixin.tools.models.receive.common.LinkMsg;
import com.dev.weixin.tools.models.receive.common.LocationMsg;
import com.dev.weixin.tools.models.receive.common.TextMsg;
import com.dev.weixin.tools.models.receive.common.VideoMsg;
import com.dev.weixin.tools.models.receive.common.VoiceMsg;
import com.dev.weixin.tools.models.receive.event.LocationEvent;
import com.dev.weixin.tools.models.receive.event.LocationSelectEvent;
import com.dev.weixin.tools.models.receive.event.MenuEvent;
import com.dev.weixin.tools.models.receive.event.PicSysphotoEvent;
import com.dev.weixin.tools.models.receive.event.SVSCEvent;
import com.dev.weixin.tools.models.receive.event.ScancodePushEvent;
import com.dev.weixin.tools.service.BaseService;
import com.dev.weixin.tools.service.GlobalReceiveHandler;
import com.dev.weixin.tools.service.ReceiveHandler;
import com.dev.weixin.tools.service.ReceiveService;
import com.dev.weixin.tools.service.common.ImageHandler;
import com.dev.weixin.tools.service.common.LinkHandler;
import com.dev.weixin.tools.service.common.LocationHandler;
import com.dev.weixin.tools.service.common.ShortVideoHandler;
import com.dev.weixin.tools.service.common.TextHandler;
import com.dev.weixin.tools.service.common.VideoHandler;
import com.dev.weixin.tools.service.common.VoiceHandler;
import com.dev.weixin.tools.service.event.ClickHandler;
import com.dev.weixin.tools.service.event.LocationEventHandler;
import com.dev.weixin.tools.service.event.LocationSelectHandler;
import com.dev.weixin.tools.service.event.PicPhotoOrAlbumHandler;
import com.dev.weixin.tools.service.event.PicSysphotoHandler;
import com.dev.weixin.tools.service.event.PicWeixinHandler;
import com.dev.weixin.tools.service.event.ScanHandler;
import com.dev.weixin.tools.service.event.ScancodePushHandler;
import com.dev.weixin.tools.service.event.ScancodeWaitmsgHandler;
import com.dev.weixin.tools.service.event.SubscribeHandler;
import com.dev.weixin.tools.service.event.UnsubscribeHandler;
import com.dev.weixin.tools.service.event.ViewHandler;
import com.dev.weixin.tools.utils.JAXBUtils;
import com.dev.weixin.tools.utils.sha1.AesException;
import com.dev.weixin.tools.utils.sha1.SHA1;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;

/**
 * @author 飘渺青衣
 * @see
 */
@Service("weixinReceiveService")
public class ReceiveServiceImpl implements ReceiveService {
	
	private Logger log = LogManager.getLogger(ReceiveServiceImpl.class);
	
	private BaseService baseService;
	
	private boolean receiveCheckSignature = true;
	
	private GlobalReceiveHandler globalReceiveHandler;
	
	private Map<String, Class<? extends BaseMsg>> clazzs = new HashMap<String, Class<? extends BaseMsg>>(); 
	
	private Map<String, ReceiveHandler<?>> handlers = new HashMap<String, ReceiveHandler<?>>();
	
	@Autowired(required = false)
	public ReceiveServiceImpl(BaseService baseService, ReceiveHandler<?>[] receiveHandlers) {
		this(baseService);
		if(receiveHandlers != null) {
			Class<?> source = null;
			Class<?> clazz = null;
			Class<?> interfaceClass = null;
			Class<?>[] interfaces = null;
			Method method = null;
			for(ReceiveHandler<?> handler : receiveHandlers) {
				if(handler instanceof Proxy) {
					source = AopUtils.getTargetClass(handler);
				} else {
					source = handler.getClass();
				}
				interfaces = source.getInterfaces();
				clazz = null;
				for(int i=0; i<interfaces.length; i++) {
					interfaceClass = interfaces[i];
					if(interfaceClass.getAnnotation(Handler.class) != null) {
						clazz = interfaceClass;
						log.debug("set{}({})",clazz.getSimpleName(), source.getName());
						break;
					}
				}
				if(clazz != null) {
					try {
						method = ReceiveServiceImpl.class.getMethod(new StringBuilder("set").append(clazz.getSimpleName()).toString(), clazz);
						method.invoke(this, handler);
					} catch (NoSuchMethodException e) {
						e.printStackTrace();
					} catch (SecurityException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (InvocationTargetException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
	public ReceiveServiceImpl(BaseService baseService) {
		this.baseService = baseService;
		clazzs.put("text", TextMsg.class);
		clazzs.put("image", ImageMsg.class);
		clazzs.put("voice", VoiceMsg.class);
		clazzs.put("video", VideoMsg.class);
		clazzs.put("shortvideo", VideoMsg.class);
		clazzs.put("location", LocationMsg.class);
		clazzs.put("link", LinkMsg.class);
		//事件
		clazzs.put("unsubscribe", EventMsg.class);
		clazzs.put("subscribe", SVSCEvent.class);
		clazzs.put("SCAN", SVSCEvent.class);
		clazzs.put("LOCATION", LocationEvent.class);
		clazzs.put("CLICK", MenuEvent.class);
		clazzs.put("VIEW", MenuEvent.class);
		clazzs.put("scancode_push", ScancodePushEvent.class);
		clazzs.put("scancode_waitmsg", ScancodePushEvent.class);
		clazzs.put("pic_sysphoto", PicSysphotoEvent.class);
		clazzs.put("pic_photo_or_album", PicSysphotoEvent.class);
		clazzs.put("pic_weixin", PicSysphotoEvent.class);
		clazzs.put("location_select", LocationSelectEvent.class);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#checkSignature(com.dev.weixin.tools.models.receive.ValidData)
	 */
	@Override
	public boolean checkSignature(String toUserName, ValidData valid) throws AesException {
		if(StringUtils.equalsIgnoreCase(
				valid.getSignature(), 
					this.buildSignature(toUserName, valid))) {
			return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#buildSignature(com.dev.weixin.tools.models.receive.ValidData)
	 */
	@Override
	public String buildSignature(String toUserName, ValidData valid) throws AesException {
		String[] array = new String[]{
				this.baseService.getWeixinConfig(toUserName).getToken(), 
				valid.getTimestamp(), 
				valid.getNonce()};
		Arrays.sort(array);
		StringBuilder s = new StringBuilder();
		for(String str:array) {
			s.append(str);
		}
		return SHA1.getSHA1(s.toString());
	}
	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#receive(com.dev.weixin.tools.models.receive.ValidData, java.lang.String)
	 */
	@Override
	public String receive(ValidData valid, InputStream is) throws AesException,
			IOException, ErrorMsgException {
		BufferedReader br = new BufferedReader(new InputStreamReader(is, WeixinConfig.ENCODING));
		StringBuilder s = new StringBuilder();
		String str = null;
		while((str = br.readLine()) != null) {
			s.append(str);
		}
		br.close();
		return receive(valid, s.toString());
	}
	
	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#receive(com.dev.weixin.tools.models.receive.ValidData, java.io.InputStream)
	 */
	@Override
	public String receive(ValidData valid, String xmlStr) throws AesException, IOException, ErrorMsgException {
		try {
			String xml = xmlStr;
			//是否密文
			boolean isEncrypt = valid.isEncrypt();
			WXBizMsgCrypt crypt = null;
			WeixinConfig config = null;
			if(isEncrypt) {
				//密文信息
				log.info(xml);
				EncryptMsg encrypt = JAXBUtils.fromXML(xml, EncryptMsg.class);
				config = this.baseService.getWeixinConfig(encrypt.getTouser());
				try {
					crypt = new WXBizMsgCrypt(config.getToken(), config.getEncodingAESKey(), config.getAppId());
					xml = crypt.decryptMsg(valid.getMsg_signature(), valid.getTimestamp(), valid.getNonce(), xml);
				} catch (com.qq.weixin.mp.aes.AesException e) {
					throw new AesException(AesException.ComputeSignatureError);
				}
			}
			log.info(xml);
			EventMsg base = JAXBUtils.fromXML(xml, EventMsg.class);
			if(this.isReceiveCheckSignature() && !this.checkSignature(base.getTouser(), valid)) {
				return "";
			}
			if(config == null) {
				config = this.baseService.getWeixinConfig(base.getTouser());
				if(config == null) {
					log.error("没有微信配置文件！");
					return "";
				}
			}
			String key = null;
			//事件
			if(StringUtils.equals("event", base.getMsgtype())) {
				key = base.getEvent();
			} else {
				key = base.getMsgtype();
			}
			Class<? extends BaseMsg> clazz = this.clazzs.get(key);
			ReceiveHandler<? extends BaseMsg> handler = this.handlers.get(key);
			BaseMsg msg = JAXBUtils.fromXML(xml, clazz);
			BaseMsg msgRes = null;
			if(handler != null) {
				try {
					Method method = handler.getClass().getMethod("handler", clazz);
					msgRes =  (BaseMsg) method.invoke(handler, new Object[]{msg});
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (SecurityException e) {
					e.printStackTrace();
				}
			} else {
				if(this.globalReceiveHandler != null) {
					msgRes = this.globalReceiveHandler.handler(msg);
				}
			}
			if(msgRes != null) {
				msgRes.setFromUserName(base.getTouser());
				msgRes.setTouser(base.getFromUserName());
				msgRes.setCreateTime(Calendar.getInstance().getTimeInMillis());
				String result = JAXBUtils.toXML(msgRes, WeixinConfig.ENCODING);
				log.info(result);
				if(isEncrypt) {
					//密文回复
					try {
						result = crypt.encryptMsg(result, valid.getTimestamp(), valid.getNonce());
					} catch (com.qq.weixin.mp.aes.AesException e) {
						throw new AesException(AesException.ComputeSignatureError);
					}
					log.info(result);
				}
				return result;
			}
			return "";
		} finally {
			this.baseService.removeThreadLocalWeixinConfig();
		}
	}

	public void setTextHandler(TextHandler textHandler) {
		this.handlers.put("text", textHandler);
	}
	
	public void removeTextHandler() {
		this.handlers.remove("text");
	}

	public void setImageHandler(ImageHandler imageHandler) {
		this.handlers.put("image", imageHandler);
	}
	
	public void removeImageHandler() {
		this.handlers.remove("image");
	}
	
	public GlobalReceiveHandler getGlobalReceiveHandler() {
		return globalReceiveHandler;
	}

	public void setGlobalReceiveHandler(GlobalReceiveHandler globalReceiveHandler) {
		this.globalReceiveHandler = globalReceiveHandler;
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setVoiceHandler(com.dev.weixin.tools.service.common.VoiceHandler)
	 */
	@Override
	public void setVoiceHandler(VoiceHandler voiceHandler) {
		this.handlers.put("voice", voiceHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeVoiceHandler()
	 */
	@Override
	public void removeVoiceHandler() {
		this.handlers.remove("voice");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setVideoHandler(com.dev.weixin.tools.service.common.VideoHandler)
	 */
	@Override
	public void setVideoHandler(VideoHandler videoHandler) {
		this.handlers.put("video", videoHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeVideoHandler()
	 */
	@Override
	public void removeVideoHandler() {
		this.handlers.remove("video");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setShortVideoHandler(com.dev.weixin.tools.service.common.ShortVideoHandler)
	 */
	@Override
	public void setShortVideoHandler(ShortVideoHandler shortVideoHandler) {
		this.handlers.put("shortvideo", shortVideoHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeShortVideoHandler()
	 */
	@Override
	public void removeShortVideoHandler() {
		this.handlers.remove("shortvideo");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setLocationHandler(com.dev.weixin.tools.service.common.LocationHandler)
	 */
	@Override
	public void setLocationHandler(LocationHandler locationHandler) {
		this.handlers.put("location", locationHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeLocationHandler()
	 */
	@Override
	public void removeLocationHandler() {
		this.handlers.remove("location");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setLinkHandler(com.dev.weixin.tools.service.common.LinkHandler)
	 */
	@Override
	public void setLinkHandler(LinkHandler linkHandler) {
		this.handlers.put("link", linkHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeLinkHandler()
	 */
	@Override
	public void removeLinkHandler() {
		this.handlers.remove("link");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setSubscribeHandler(com.dev.weixin.tools.service.event.SubscribeHandler)
	 */
	@Override
	public void setSubscribeHandler(SubscribeHandler subscribeHandler) {
		this.handlers.put("subscribe", subscribeHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeSubscribeHandler()
	 */
	@Override
	public void removeSubscribeHandler() {
		this.handlers.remove("subscribe");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setUnsubscribeHandler(com.dev.weixin.tools.service.event.UnsubscribeHandler)
	 */
	@Override
	public void setUnsubscribeHandler(UnsubscribeHandler unsubscribeHandler) {
		this.handlers.put("unsubscribe", unsubscribeHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeUnsubscribeHandler()
	 */
	@Override
	public void removeUnsubscribeHandler() {
		this.handlers.remove("unsubscribe");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setScanHandler(com.dev.weixin.tools.service.event.ScanHandler)
	 */
	@Override
	public void setScanHandler(ScanHandler scanHandler) {
		this.handlers.put("SCAN", scanHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeScanHandler()
	 */
	@Override
	public void removeScanHandler() {
		this.handlers.remove("SCAN");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setLocationEventHandler(com.dev.weixin.tools.service.event.LocationEventHandler)
	 */
	@Override
	public void setLocationEventHandler(
			LocationEventHandler locationEventHandler) {
		this.handlers.put("LOCATION", locationEventHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeLocationEventHandler()
	 */
	@Override
	public void removeLocationEventHandler() {
		this.handlers.remove("LOCATION");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setClickHandler(com.dev.weixin.tools.service.event.ClickHandler)
	 */
	@Override
	public void setClickHandler(ClickHandler clickHandler) {
		this.handlers.put("CLICK", clickHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeClickHandler()
	 */
	@Override
	public void removeClickHandler() {
		this.handlers.remove("CLICK");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setViewHandler(com.dev.weixin.tools.service.event.ViewHandler)
	 */
	@Override
	public void setViewHandler(ViewHandler viewHandler) {
		this.handlers.put("VIEW", viewHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeViewHandler()
	 */
	@Override
	public void removeViewHandler() {
		this.handlers.remove("viewHandler");
	}

	public boolean isReceiveCheckSignature() {
		return receiveCheckSignature;
	}

	public void setReceiveCheckSignature(boolean receiveCheckSignature) {
		this.receiveCheckSignature = receiveCheckSignature;
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setScancodePushHandler(com.dev.weixin.tools.service.event.ScancodePushHandler)
	 */
	@Override
	public void setScancodePushHandler(ScancodePushHandler scancodePushHandler) {
		this.handlers.put("scancode_push", scancodePushHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeScancodePushHandler()
	 */
	@Override
	public void removeScancodePushHandler() {
		this.handlers.remove("scancode_push");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setScancodeWaitmsgHandler(com.dev.weixin.tools.service.event.ScancodeWaitmsgHandler)
	 */
	@Override
	public void setScancodeWaitmsgHandler(
			ScancodeWaitmsgHandler scancodeWaitmsgHandler) {
		this.handlers.put("scancode_waitmsg", scancodeWaitmsgHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeScancodeWaitmsgHandler()
	 */
	@Override
	public void removeScancodeWaitmsgHandler() {
		this.handlers.remove("scancode_waitmsg");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setPicSysphotoHandler(com.dev.weixin.tools.service.event.PicSysphotoHandler)
	 */
	@Override
	public void setPicSysphotoHandler(PicSysphotoHandler picSysphotoHandler) {
		this.handlers.put("pic_sysphoto", picSysphotoHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removePicSysphotoHandler()
	 */
	@Override
	public void removePicSysphotoHandler() {
		this.handlers.remove("pic_sysphoto");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setPicPhotoOrAlbumHandler(com.dev.weixin.tools.service.event.PicPhotoOrAlbumHandler)
	 */
	@Override
	public void setPicPhotoOrAlbumHandler(
			PicPhotoOrAlbumHandler picPhotoOrAlbumHandler) {
		this.handlers.put("pic_photo_or_album", picPhotoOrAlbumHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removePicPhotoOrAlbumHandler()
	 */
	@Override
	public void removePicPhotoOrAlbumHandler() {
		this.handlers.remove("pic_photo_or_album");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setPicWeixinHandler(com.dev.weixin.tools.service.event.PicWeixinHandler)
	 */
	@Override
	public void setPicWeixinHandler(PicWeixinHandler picWeixinHandler) {
		this.handlers.put("pic_weixin", picWeixinHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removePicWeixinHandler()
	 */
	@Override
	public void removePicWeixinHandler() {
		this.handlers.remove("pic_weixin");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#setLocationSelectHandler(com.dev.weixin.tools.service.event.LocationSelectHandler)
	 */
	@Override
	public void setLocationSelectHandler(
			LocationSelectHandler locationSelectHandler) {
		this.handlers.put("location_select", locationSelectHandler);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#removeLocationSelectHandler()
	 */
	@Override
	public void removeLocationSelectHandler() {
		this.handlers.remove("location_select");
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#checkSignature(com.dev.weixin.tools.models.receive.ValidData)
	 */
	@Override
	public boolean checkSignature(ValidData valid) throws AesException {
		return this.checkSignature(null, valid);
	}

	/* (non-Javadoc)
	 * @see com.dev.weixin.tools.service.ReceiveService#buildSignature(com.dev.weixin.tools.models.receive.ValidData)
	 */
	@Override
	public String buildSignature(ValidData valid) throws AesException {
		return this.buildSignature(null, valid);
	}

}
